package pers.cz.javacvdemo.redis;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.RedisStringCommands.BitOperation;
import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.types.Expiration;
import pers.cz.javacvdemo.utils.ReflectUtils;

import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author yiyi.su
 * @version 1.0.0
 * @description StringOp
 * @createTime 2022/8/15
 */
@Slf4j
public class StringOp extends Operation {

	private final ValueOperations<String, String> STRING;

	private final StringRedisTemplate redisTemplate;

	public StringOp(StringRedisTemplate redisTemplate) {
		this.STRING = redisTemplate.opsForValue();
		this.redisTemplate = redisTemplate;
	}

	public byte[] rawKey(String key) {
		byte[] rawKey = null;
		try {
			rawKey = (byte[]) ReflectUtils.invoke(STRING, "rawKey", key);
		} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) {
			rawKey = key.getBytes(StandardCharsets.UTF_8);
		}
		return rawKey;
	}

	public byte[] rawValue(String value) {
		byte[] rawKey = null;
		try {
			rawKey = (byte[]) ReflectUtils.invoke(STRING, "rawValue", value);
		} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ignored) {
			rawKey = value.getBytes(StandardCharsets.UTF_8);
		}
		return rawKey;
	}

	public Integer append(String key, String value) {
		try {
			return STRING.append(key, value);
		} catch (Exception e) {
			log.warn("append exception, key={},value={}", key, value, e);
		}
		return null;
	}

	public Long bitCount(String key, int start, int end) {
		try {
			return redisTemplate.execute((RedisCallback<Long>) connection -> connection.stringCommands().bitCount(rawKey(key), start, end));
		} catch (Exception e) {
			log.warn("bitCount exception, key={},start={}，end={}", key, start, end, e);
		}
		return null;
	}

	public List<Long> bitField(String key, final BitFieldSubCommands subCommands) {
		try {
			return STRING.bitField(key, subCommands);
		} catch (Exception e) {
			log.warn("bitField exception, key={},subCommands={}", key, subCommands, e);
		}
		return null;
	}

	public Long bitOp(BitOperation op, String destination, String... keys) {
		try {
			byte[][] keyArr = new byte[0][0];
			if (ArrayUtils.isNotEmpty(keys)) {
				keyArr = Arrays.stream(keys).map(this::rawKey).toArray(byte[][]::new);
			}
			byte[][] finalKeyArr = keyArr;
			return redisTemplate.execute((RedisCallback<Long>) con -> con.stringCommands().bitOp(op, rawKey(destination), finalKeyArr));
		} catch (Exception e) {
			log.warn("bitOp exception, keys={},op={},destination={}", keys, op, destination, e);
		}
		return null;
	}

	public Long bitPos(String key, boolean bit, Range<Long> range) {
		try {
			return redisTemplate.execute((RedisCallback<Long>) con -> con.stringCommands().bitPos(rawKey(key), bit, range));
		} catch (Exception e) {
			log.warn("bitPos exception, key={},bit={},range={}", key, bit, range, e);
		}
		return null;
	}

	public Long decr(String key) {
		try {
			return STRING.decrement(key);
		} catch (Exception e) {
			log.warn("decr exception, key={}", key, e);
		}
		return null;

	}

	public Long decrBy(String key, long delta) {
		try {
			return STRING.decrement(key, delta);
		} catch (Exception e) {
			log.warn("decrBy exception, key={}", key, e);
		}
		return null;
	}

	public <T> T get(String key, Class<T> clz) {
		try {
			return deserialize(STRING.get(key), clz);
		} catch (Exception e) {
			log.warn("get exception, key={}", key, e);
		}
		return null;

	}

	public String get(String key) {
		try {
			return STRING.get(key);
		} catch (Exception e) {
			log.warn("get exception, key={}", key, e);
		}
		return null;
	}

	public Boolean getBit(String key, long offset) {
		try {
			return STRING.getBit(key, offset);
		} catch (Exception e) {
			log.warn("getBit exception, key={},offset={}", key, offset, e);
		}
		return null;
	}

	public String getRange(String key, int start, int end) {
		try {
			return STRING.get(key, start, end);
		} catch (Exception e) {
			log.warn("getRange exception, key={},start={},end={}", key, start, end, e);
		}
		return null;

	}

	public <T> T getSet(String key, Object obj, Class<T> clz) {
		try {
			String oldValue = STRING.getAndSet(key, serialize(obj));
			return deserialize(oldValue, clz);
		} catch (Exception e) {
			log.warn("getSet exception, key={},obj={}", key, obj, e);
		}
		return null;
	}

	public Long incr(String key) {
		try {
			return STRING.increment(key);
		} catch (Exception e) {
			log.warn("incr exception, key={}", key, e);
		}
		return null;

	}

	public Long incrBy(String key, long delta) {
		try {
			return STRING.increment(key, delta);
		} catch (Exception e) {
			log.warn("incrBy exception, key={},delta={}", key, delta, e);
		}
		return null;

	}

	public Double incrBy(String key, double delta) {
		try {
			return STRING.increment(key, delta);
		} catch (Exception e) {
			log.warn("incrBy exception, key={},delta={}", key, delta, e);
		}
		return null;
	}

	public <T> List<T> mGet(Collection<String> keys, Class<T> clz) {
		try {
			List<String> list = STRING.multiGet(keys);
			if (list == null) {
				return new ArrayList<>();
			}
			return list.stream().map(str -> deserialize(str, clz)).collect(Collectors.toList());
		} catch (Exception e) {
			log.warn("mGet exception, keys={}", keys, e);
		}
		return null;
	}

	public void mSet(Map<String, Object> map) {
		try {
			if (MapUtils.isEmpty(map)) {
				return;
			}
			Map<String, String> mValues = new LinkedHashMap<>(map.size());
			for (Entry<String, Object> entry : map.entrySet()) {
				mValues.put(entry.getKey(), serialize(entry.getValue()));
			}
			STRING.multiSet(mValues);
		} catch (Exception e) {
			log.warn("mSet exception, map={}", map, e);
		}
	}

	public Boolean mSetNX(Map<String, Object> map) {
		try {
			if (MapUtils.isEmpty(map)) {
				return true;
			}
			Map<String, String> mValues = new LinkedHashMap<>(map.size());
			for (Entry<String, Object> entry : map.entrySet()) {
				mValues.put(entry.getKey(), serialize(entry.getValue()));
			}
			return STRING.multiSetIfAbsent(mValues);
		} catch (Exception e) {
			log.warn("mSetNX exception, map={}", map, e);
		}
		return null;
	}

	public void set(String key, Object value) {
		try {
			STRING.set(key, serialize(value));
		} catch (Exception e) {
			log.warn("set exception, key={},value={}", key, value, e);
		}
	}

	public void setEX(String key, Object value, long timeout, TimeUnit unit) {
		try {
			STRING.set(key, serialize(value), timeout, unit);
		} catch (Exception e) {
			log.warn("setEX exception, key={},value={},timeout={},unit={}", key, value, timeout, unit, e);
		}

	}

	public Boolean set(String key, Object value, Expiration expiration, SetOption option) {
		try {
			return redisTemplate.execute((RedisCallback<Boolean>) connection -> connection.stringCommands().set(rawKey(key), rawKey(serialize(value)), expiration, option));
		} catch (Exception e) {
			log.warn("set exception, key={},value={},expiration={},option={}", key, value, expiration, option, e);
		}
		return null;
	}

	public Boolean setPX(String key, Object value) {
		return set(key, value, Expiration.persistent(), SetOption.ifPresent());
	}

	public Boolean setBit(String key, long offset, boolean value) {
		try {
			return STRING.setBit(key, offset, value);
		} catch (Exception e) {
			log.warn("setBit exception, key={},offset={},value={}", key, offset, value, e);
		}
		return null;
	}

	public Boolean setNX(String key, Object value) {
		try {
			return STRING.setIfAbsent(key, serialize(value));
		} catch (Exception e) {
			log.warn("setNX exception, key={},value={}", key, value, e);
		}
		return null;
	}

	public void setRange(String key, Object value, long offset) {
		try {
			STRING.set(key, serialize(value), offset);
		} catch (Exception e) {
			log.warn("setRange exception, key={},value={},offset={}", key, value, offset, e);
		}
	}

	public Long strLen(String key) {
		try {
			return STRING.size(key);
		} catch (Exception e) {
			log.warn("strLen exception, key={}", key, e);
		}
		return null;
	}

	public <T> T getDel(String key, Class<T> clz) {
		try {
			return deserialize(STRING.getAndDelete(key), clz);
		} catch (Exception e) {
			log.warn("getDel exception, key={}", key, e);
		}
		return null;
	}

	public <T> T getEX(String key, long timeout, TimeUnit unit, Class<T> clz) {
		try {
			return deserialize(STRING.getAndExpire(key, timeout, unit), clz);
		} catch (Exception e) {
			log.warn("getEX exception, key={},timeout={},unit={}", key, timeout, unit, e);
		}
		return null;
	}
}
